package cc.wanforme.chipmunity.security.handlers;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import cc.wanforme.chipmunity.common.IpUtils;
import cc.wanforme.chipmunity.common.PassEncoder;
import cc.wanforme.chipmunity.common.TokenUtil;
import cc.wanforme.chipmunity.security.service.PersistentLoginService;
import cc.wanforme.chipmunity.system.po.user.LoginLog;
import cc.wanforme.chipmunity.system.po.user.PersistentLogin;
import cc.wanforme.chipmunity.system.service.interfaces.LoginLogService;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.StreamUtils;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Calendar;
import java.util.Date;

/**
 * 自定义 JSON 登录
 */
public class CustomJSONLoginFilter extends AbstractAuthenticationProcessingFilter {
	private static final Logger log = LoggerFactory.getLogger(CustomJSONLoginFilter.class);
	
/*    private final UserService userService;

    CustomJSONLoginFilter(String defaultFilterProcessesUrl, UserService userService) {
        super(new AntPathRequestMatcher(defaultFilterProcessesUrl, HttpMethod.POST.name()));
        this.userService = userService;
    }
*/
	private final String[] REMEMBER_ME_TRUE = {"on", "true", "checked"};
//	private final String[] REMEMBER_ME_FALSE = {"off", "false", "unchecked"};
	
	private final UserDetailsService userDetailsService;
	private final LoginLogService loginLogService;
	private final PersistentLoginService persistentLoginService;
	
    public CustomJSONLoginFilter(String defaultFilterProcessesUrl, UserDetailsService userDetailsService, 
    			LoginLogService loginLogService, PersistentLoginService persistentLoginService) {
        super(new AntPathRequestMatcher(defaultFilterProcessesUrl, HttpMethod.POST.name()));
        this.userDetailsService = userDetailsService;
        this.loginLogService = loginLogService;
        this.persistentLoginService = persistentLoginService;
    }
	
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
    	/* 大佬的方法好像有问题啊，Jquery使用ajax请求没获取到参数，索性就换成了原始的取参方式
    	 * 补充：2019-7-24
    	 * 没有问难题，有个屁的问题，之前的问题出在Jquery使用ajax时，contentType不是"application/json;charset-UTF-8"和发送的数据是json对象（正确应该是json格式字符串）*/
    	JSONObject requestBody = getRequestBody(request);
        String username = requestBody.getString("username");
        String password = requestBody.getString("password");
        String remember_me = requestBody.getString("remember-me");
        
    	/* 这种原生的方式，似乎支持更好
    	 *  补充：2019-7-24 
    	 *  不建议这样使用，因为@RequsetBody接收的是json格式的字符串，尽量保持统一
    	String username = httpServletRequest.getParameter("username");
        String password = httpServletRequest.getParameter("password");
        */
        
        //密码加密
        try {
        	password = PassEncoder.encodePassword(password);
		} catch (UnsupportedEncodingException e) {
			log.info("确认密码加密失败！");
			log.error(e.getMessage());
			throw new UsernameNotFoundException("confirm password encode fail ! ");
		}
        
        // 进行验证
        //validateUsernameAndPassword(username, password);//调用自定义的验证方法
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        if(null == userDetails ) {
        	throw new UsernameNotFoundException("User not exist! ");
        }
        if(!userDetails.getPassword().equals(password)) {
        	throw new AuthenticationServiceException("error username or password! ");
        }
        
        // token处理和添加登录记录应该放到认证成功的处理器中
        
        //设置 Token ？
        if(remember_me!=null && StringUtils.containsAny(remember_me, REMEMBER_ME_TRUE)) {
        	setToken(username ,response);
        }else{
        	removeToken(username, response);
        }
        
        //添加登录记录
        /* remoteHost - 主机名，解析不了时会返回ip地址， remoteAddr - IP地址，
         * 如果经过apache、ngix代理，那么将获取不到真实地址
         * LoginLog loginLog = new LoginLog(username, address, requset.getRemoteAddr(), new Date());
         */
        LoginLog loginLog = new LoginLog(username, IpUtils.getAddress(request), IpUtils.getIpAddr(request), new Date());
        loginLogService.saveLoginLog(loginLog);
        
      /*  List<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>();
        simpleGrantedAuthorities.add(new SimpleGrantedAuthority("USER"));
        return new UsernamePasswordAuthenticationToken(username, password, simpleGrantedAuthorities);
        */
        return new UsernamePasswordAuthenticationToken(username, password, userDetails.getAuthorities());
    }


	/**
     * 获取请求体（和springboot一致，接收到的前端数据是json格式的字符串，而不是json对象）
     */
    private JSONObject getRequestBody(HttpServletRequest request) throws AuthenticationException{
        try {
            StringBuilder stringBuilder = new StringBuilder();
            InputStream inputStream = request.getInputStream();
            byte[] bs = new byte[StreamUtils.BUFFER_SIZE];
            int len;
            while ((len = inputStream.read(bs)) != -1) {
                stringBuilder.append(new String(bs, 0, len));
            }
            
            return JSON.parseObject(stringBuilder.toString());
        } catch (IOException e) {
            log.error("get request body error.");
        }
        throw new AuthenticationServiceException("invalid request body");
    }

    /**
     * 校验用户名和密码，返回
    
    private void validateUsernameAndPassword(String username, String password) throws AuthenticationException {
    	/* 这里是使用了自定义的User po实体，和User对应的Service，用于从数据库获取信息，并进行验证
        UserDO userDO = userService.getByUsername(username);
        if (userDO == null){
            throw new UsernameNotFoundException("user not exist");
        }
        if(!userDO.getPassword().equals(password)){
            throw new AuthenticationServiceException("error username or password");
        }
        *
    	/*这里的用了之前的写的UserDetailsService，里面从数据库获取信息，然后在这里验，也可以像上面那样自定义*
    	
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        if(null == userDetails ) {
        	throw new UsernameNotFoundException("User not exist! ");
        }
        if(!userDetails.getPassword().equals(password)) {
        	throw new AuthenticationServiceException("error username or password! ");
        }
    }
	 */
    
    
    /**
     * 设置token
     */
    protected void setToken( String username, HttpServletResponse response) {
    	PersistentLogin persistentLogin = persistentLoginService.verifyTokenByUsername(username);
    	Cookie cookie = null;
    	
    	//先检查，如果存在记录
    	if(persistentLogin == null) {
    		Calendar calendar = Calendar.getInstance();
    		Date now = calendar.getTime();
    		calendar.add(Calendar.MONTH, 1);
    		String token = TokenUtil.createToken(username);
    		persistentLogin = new PersistentLogin(
    				username, token, now, calendar.getTime());
    		
    		cookie = new Cookie(CustomTokenFilter.TOKEN_NAME, token);
    		persistentLoginService.save(persistentLogin);
    	}else {
       		Calendar calendar = Calendar.getInstance();
    		Date now = calendar.getTime();
    		calendar.add(Calendar.MONTH, 1);
    		String token = TokenUtil.createToken(username);
    		persistentLogin.setToken(token);
    		persistentLogin.setSetTime(now);
    		persistentLogin.setExpire(calendar.getTime());
    		
    		cookie = new Cookie(CustomTokenFilter.TOKEN_NAME, token);
    		persistentLoginService.update(persistentLogin);
    	}
    	
    	//设置到cookie（设置httpOnly）
    	cookie.setHttpOnly(true);
    	cookie.setMaxAge(60*60*24*30); // 30天
    	response.addCookie(cookie); 
    	
	}

    /** 移除token*/
    private void removeToken(String username, HttpServletResponse response) {
    	persistentLoginService.removePersistentLoginByUsername(username);
    	Cookie cookie = new Cookie(CustomTokenFilter.TOKEN_NAME, null);
    	response.addCookie(cookie);
	}
}
